/*
 ref link:
    https://blog.csdn.net/leixiaohua1020/article/details/39702113
    https://blog.csdn.net/xiaolong1126626497/article/details/104919095
    https://zhuanlan.zhihu.com/p/102489856
*/
#include <stdio.h>
#include <stdlib.h>

#include <SDL2/SDL.h>
#include <SDL2/SDL_video.h>
#include <SDL2/SDL_render.h>
#include <SDL2/SDL_rect.h>
#include <SDL2/SDL_image.h>

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
#include <libavdevice/avdevice.h>

#define YUV_WIDTH 352
#define YUV_HEIGHT 288

#define SDL_REFRESH_EVENT (SDL_USEREVENT + 1)
#define SDL_BREAK_EVENT (SDL_USEREVENT + 1)

const int flag = 1;

int control_thread(void* args){
    while(flag == 1){
        SDL_Event event_tmp;
        event_tmp.type = SDL_REFRESH_EVENT;
        int ret_tmp = SDL_PushEvent(&event_tmp);
        if(ret_tmp != 1){
            fprintf(stderr, "SDL_PushEvent ERROR\n");
            break;
        }
        SDL_Delay(40);
    }
    return 0;
};

int main(int argc, char* argv[]){
    if(argc < 2){
        printf("Please input YUV file!\n");
        return -1;
    }

    int ret = 0;
    FILE* fp = NULL;
    char* url = NULL;

    int win_width = YUV_WIDTH;
    int win_height = YUV_HEIGHT;
    SDL_Window* p_window = NULL;
    SDL_Renderer* p_render = NULL;
    SDL_Texture* p_texture = NULL;
    SDL_Thread* p_sdl_thread = NULL;
    SDL_Event main_event;
    SDL_Rect rect;

    AVFormatContext *pfmt_ctx = NULL;
    AVInputFormat *infmt = NULL;
    AVCodecParameters *pcodec_par = NULL;
    AVCodecContext *pcodec_ctx = NULL;
    AVCodec *pcodec = NULL;
    AVPacket *ppkt = NULL;
    AVFrame *pfrm = NULL;
    AVFrame *pyuv_frm = NULL;
    uint8_t *frm_data[4] = {NULL};
    int frm_linesize[4] = {0};
    int buffer_size = 0;

    struct SwsContext *psws_ctx = NULL;
    int video_idx = -1;
    int flag = 1;
    int how_many_frames_to_capture = 100;

    url = argv[1];

    ret = SDL_Init(SDL_INIT_VIDEO);
    if(ret != 0){
        fprintf(stderr, "SDL_Init ERROR\n");
        return -1;
    }
    p_window = SDL_CreateWindow("YUV Player", 
                                    SDL_WINDOWPOS_UNDEFINED,
                                    SDL_WINDOWPOS_UNDEFINED,
                                    YUV_WIDTH,
                                    YUV_HEIGHT,
                                    SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);

    if(p_window == NULL){
        fprintf(stderr, "SDL_CreateWindow Error\n");
        return -1;
    }

    p_render = SDL_CreateRenderer(p_window, -1, SDL_RENDERER_ACCELERATED);
    if(p_render == NULL){
        fprintf(stderr, "SDL_CreateRenderer ERROR\n");
        return -1;
    }

    p_texture = SDL_CreateTexture(p_render, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, YUV_WIDTH, YUV_HEIGHT);
    if(p_texture == NULL){
        fprintf(stderr, "SDL_CreateTexture ERROR\n");
        return -1;
    }

    fp = fopen(url, "wb");
    if(fp == NULL){
        fprintf(stderr, "File open ERROR\n");
        return -1;
    }

    p_sdl_thread = SDL_CreateThread(control_thread, "Control Thread", NULL);

    int frame_idx = 0;

    avdevice_register_all();
    
    if ((pfmt_ctx = avformat_alloc_context()) == NULL) {
        av_log(NULL, AV_LOG_ERROR, "avformat_alloc_context ERROR \n");
        return AVERROR(ENOMEM);
    }

    if ((infmt = av_find_input_format("video4linux2")) == NULL) {
        av_log(NULL, AV_LOG_ERROR, "av_find_input_format ERROR \n");
        return AVERROR(EINVAL);
    }

    if((ret = avformat_open_input(&pfmt_ctx, "/dev/video0", infmt, NULL)) < 0) {
        av_log(NULL, AV_LOG_ERROR, "avformat_open_input ERROR \n");
        return AVERROR(EINVAL);
    }

    if ((ret = avformat_find_stream_info(pfmt_ctx, NULL)) < 0) {
        av_log(NULL, AV_LOG_ERROR, "avformat_find_stream_info ERROR \n");
        return AVERROR(EINVAL);
    }
    
    for (int i=0; i<pfmt_ctx->nb_streams; i++) {
        if (pfmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            video_idx = i;
            break;
        }
    }

    if (video_idx == -1) {
        fprintf(stderr, "Can not find video index \n");
        return -1;
    }

    pcodec_par = pfmt_ctx->streams[video_idx]->codecpar;

    if ((pcodec = avcodec_find_decoder(pcodec_par->codec_id)) == NULL) {
        av_log(NULL, AV_LOG_ERROR, "avcodec_find_encoder ERROR \n");
        return AVERROR(ret);
    }
    
    if ((pcodec_ctx = avcodec_alloc_context3(pcodec)) == NULL) {
        av_log(NULL, AV_LOG_ERROR, "avcodec_alloc_context3 ERROR \n");
        return AVERROR(ENOMEM);
    }

    if ((ret = avcodec_parameters_to_context(pcodec_ctx, pcodec_par)) < 0) {
        av_log(NULL, AV_LOG_ERROR, "avcodec_parameters_to_context ERROR \n");
        return AVERROR(ret);
    }

    if ((ret = avcodec_open2(pcodec_ctx, pcodec, NULL)) < 0) {
        av_log(NULL, AV_LOG_ERROR, "avcodec_open2 ERROR \n");
        return AVERROR(ret);
    }

    if ((ppkt = av_packet_alloc()) == NULL) {
        av_log(NULL, AV_LOG_ERROR, "av_packet_alloc ERROR \n");
        return AVERROR(ENOMEM);
    }

    if ((pfrm = av_frame_alloc()) == NULL) {
        av_log(NULL, AV_LOG_ERROR, "av_frame_alloc ERROR \n");
        return AVERROR(ENOMEM);
    }

    if ((pyuv_frm = av_frame_alloc()) == NULL) {
        av_log(NULL, AV_LOG_ERROR, "av_frame_alloc ERROR \n");
        return AVERROR(ENOMEM);
    }

    if ((fp = fopen(url, "wb")) == NULL) {
        fprintf(stderr, "Can not open file %s \n", url);
        return -2;
    }

    if ((psws_ctx = sws_getContext(pcodec_ctx->width, pcodec_ctx->height, pcodec_ctx->pix_fmt, YUV_WIDTH, YUV_HEIGHT, AV_PIX_FMT_YUV420P, SWS_FAST_BILINEAR, NULL, NULL, NULL)) == NULL) {
        av_log(NULL, AV_LOG_ERROR, "sws_getContext ERROR \n");
        return AVERROR(EINVAL);
    }

    if ((ret = av_image_alloc(pyuv_frm->data, pyuv_frm->linesize, YUV_WIDTH, YUV_HEIGHT, AV_PIX_FMT_YUV420P, 1)) < 0) {
        av_log(NULL, AV_LOG_ERROR, "av_image_alloc ERROR \n");
        return AVERROR(EINVAL);
    }

    if ((ret = av_image_alloc(frm_data, frm_linesize, YUV_WIDTH, YUV_HEIGHT, AV_PIX_FMT_YUV420P, 1)) < 0) {
        av_log(NULL, AV_LOG_ERROR, "av_image_alloc ERROR \n");
        return AVERROR(EINVAL);
    }

    buffer_size = ret;
    printf("buffer size : %d \n", buffer_size);

    while(flag){
        SDL_WaitEvent(&main_event);
        if(main_event.type == SDL_REFRESH_EVENT){
            while ((ret = av_read_frame(pfmt_ctx, ppkt)) >= 0) {
                if (ppkt->stream_index == video_idx) {
                    if ((ret = avcodec_send_packet(pcodec_ctx, ppkt)) < 0) {
                        av_log(NULL, AV_LOG_ERROR, "avcodec_send_packet ERROR %s \n", av_err2str(ret));
                        return AVERROR(ret);
                    }

                    while (ret >= 0) {
                        ret = avcodec_receive_frame(pcodec_ctx, pfrm);
                        if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN)) {
                            break;
                        } else if (ret < 0) {
                            av_log(NULL, AV_LOG_ERROR, "avcodec_receive_frame ERROR \n");
                            return AVERROR(ret);
                        }

                        sws_scale(psws_ctx, (const uint8_t * const *) pfrm->data, pfrm->linesize, 0, pcodec_ctx->height, pyuv_frm->data, pyuv_frm->linesize);

                        av_image_copy(frm_data, frm_linesize, (const uint8_t **)pyuv_frm->data, pyuv_frm->linesize, AV_PIX_FMT_YUV420P, YUV_WIDTH, YUV_HEIGHT);

                        rect.x = 0;
                        rect.y = 0;
                        rect.w = win_width;
                        rect.h = win_height;

                        fwrite(frm_data[0], 1, buffer_size, fp);

                        frame_idx++;

                        SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Current frame idx is:%d", frame_idx);

                        SDL_UpdateTexture(p_texture, NULL, frm_data[0], YUV_WIDTH);
                        SDL_RenderClear(p_render);
                        SDL_RenderCopy(p_render, p_texture, NULL, &rect);
                        SDL_RenderPresent(p_render);
                        SDL_Delay(40);

                        av_frame_unref(pfrm);

                    }
                }
                av_packet_unref(ppkt);

                if (frame_idx == how_many_frames_to_capture) {
                    flag = 0;
                    break;
                }
            }

        } else if(main_event.type == SDL_WINDOWEVENT){
            SDL_GetWindowSize(p_window, &win_width, &win_height);
        } else if(main_event.type == SDL_QUIT){
            break;
        }
    }

    SDL_DetachThread(p_sdl_thread);
    SDL_DestroyWindow(p_window);
    SDL_Quit();
    fclose(fp);

    avformat_close_input(&pfmt_ctx);
    avformat_free_context(pfmt_ctx);
    av_frame_free(&pfrm);
    av_frame_free(&pyuv_frm);
    av_packet_free(&ppkt);
    sws_freeContext(psws_ctx);
    av_free(frm_data[0]);
    return 0;
}

